﻿using Actor.Net;
using Actor.Net.Binder;
using Actor.Net.Message;
using Actor.Net.Network.Actor;
using Castle.DynamicProxy.Internal;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// Actor Provider扩展类
/// </summary>
public static class ActorProviderHelper
{
    private static ConcurrentDictionary<Type, ConcurrentDictionary<object, string>> actorStringIdList { get; } = new ConcurrentDictionary<Type, ConcurrentDictionary<object, string>>();
    private static ConcurrentDictionary<string, object> actorIdList { get; } = new ConcurrentDictionary<string, object>();

    /// <summary>
    /// 对Actor类型进行绑定，注册每一个Actor实现的方法的路由
    /// </summary>
    /// <param name="provider"></param>
    /// <param name="assemblys">传入Actor实现的程序集，如果服务引用了多个Actor实现工程，应该传入多个程序集</param>
    /// <returns></returns>
    public static ActorProvider BindActor(this ActorProvider provider, params Assembly[] assemblys)
    {
        var typeList = new List<Type>();
        foreach(var assembly in assemblys)
        {
            var types = assembly.GetTypes();
            foreach (var type in types)
            {
                if (!typeof(IActor).IsAssignableFrom(type))
                    continue;

                if (typeof(IActor) == type)
                    continue;

                if (type.IsAbstract)
                    continue;

                if (type.IsInterface)
                    continue;

                typeList.Add(type);
            }
        }

        foreach (var type in typeList)
        {
            var serverGroupAttribute = type.GetCustomAttribute<ActorGroupAttribute>();
            var serverGroup = serverGroupAttribute == null ? "DefaultGroup" : serverGroupAttribute.ServerGroup;
            var interfaceType = type.GetAllInterfaces().Where(t => typeof(IActor).IsAssignableFrom(t) && t != typeof(IActor)).SingleOrDefault();
            ActorBinder.Bind(type, interfaceType, serverGroup);
            provider.LocationClient.AddServerGroup(serverGroup);
        }

        return provider;
    }

    /// <summary>
    /// 注册未捕获异常处理
    /// </summary>
    /// <param name="provider"></param>
    /// <param name="exceptionHandler"></param>
    /// <returns></returns>
    public static ActorProvider AddExceptionHandler(this ActorProvider provider, Action<ActorChannelContext, Exception> exceptionHandler)
    {
        provider.ExceptionHandler += exceptionHandler;
        return provider;
    }

    /// <summary>
    /// 注册Actor启动成功事件回调
    /// </summary>
    /// <param name="provider"></param>
    /// <param name="handler"></param>
    public static ActorProvider AddActorStartHandler(this ActorProvider provider, Action handler)
    {
        provider.AddActorStartHandler += handler;
        return provider;
    }

    /// <summary>
    /// 注册一个Actor，新建的Actor会被注册到位置服务中，在同一分组中的服务可以调用。
    /// </summary>
    /// <typeparam name="TActor">Actor实现类或者Actor接口</typeparam>
    /// <param name="provider">Actor提供类</param>
    /// <param name="actorId">Actor Id</param>
    /// <returns></returns>
    public static async Task<TActor> AddActor<TActor>(this ActorProvider provider, object actorId = null) where TActor : IActor
    {
        var actorType = typeof(TActor).IsClass ? typeof(TActor) : ActorBinder.GetActorTypeByInterface(typeof(TActor));
        var actorIdStr = GetActorStringId(actorType, actorId);

        var typeInfo = ActorBinder.GetActorTypeInfo(actorType);
        if (typeInfo == null)
            throw new Exception($"Actor {actorType.Name} not bind.");

        IActor instanceActor = ActorBinder.GetActor(typeInfo.ServerGroup, typeInfo.ActorType, actorIdStr);
        if (instanceActor != null)
            return (TActor)instanceActor;

        var routerInfo = new ActorRouterInfo
        {
            ServerGroup = typeInfo.ServerGroup,
            ActorType = typeInfo.ActorType,
            ActorId = actorIdStr,
        };
        var locationContext = await ActorProviderService.LocationClient.RegisterLocalActor(routerInfo);
        var actor = (TActor)Activator.CreateInstance(actorType);
        actor.IsLocal = true;
        actor.LocationContext = locationContext;
        ActorBinder.AddActor(typeInfo.ServerGroup, typeInfo.ActorType, actorIdStr, actor);
        return actor;
    }

    /// <summary>
    /// 获取一个Actor，在同一分组中的服务可以调用。
    /// </summary>
    /// <typeparam name="TActor">Actor实现类或者Actor接口</typeparam>
    /// <param name="provider">Actor提供类</param>
    /// <param name="actorId">Actor Id</param>
    /// <returns></returns>
    public static async Task<TActor> GetActor<TActor>(this ActorProvider provider, object actorId = null) where TActor : IActor
    {
        var actorType = typeof(TActor).IsClass ? typeof(TActor) : ActorBinder.GetActorTypeByInterface(typeof(TActor));
        var interfaceType = typeof(TActor).IsClass ? ActorBinder.GetInterfaceTypeByActor(typeof(TActor)) : typeof(TActor);
        var actorIdStr = GetActorStringId(actorType, actorId);

        var typeInfo = ActorBinder.GetActorTypeInfo(actorType);
        if (typeInfo == null)
            throw new Exception($"Actor {actorType.Name} not bind.");

        IActor instanceActor = ActorBinder.GetActor(typeInfo.ServerGroup, typeInfo.ActorType, actorIdStr);
        if (instanceActor != null)
        {
            if (instanceActor.IsLocal)
            {
                return (TActor)instanceActor;
            }
            else
            {
                return (TActor)instanceActor.ProxyInfo.ActorProxy;
            }
        }

        var routerInfo = new ActorRouterInfo
        {
            ServerGroup = typeInfo.ServerGroup,
            ActorType = typeInfo.ActorType,
            ActorId = actorIdStr,
        };
        var locationContext = await ActorProviderService.LocationClient.GetLocationContext(routerInfo);
        if (locationContext == null)
            return default;

        var factory = new ActorProxyFactory();
        var proxyInfo = factory.CreateActorProxy(actorType, interfaceType, locationContext);
        ActorBinder.AddActor(typeInfo.ServerGroup, typeInfo.ActorType, actorIdStr, proxyInfo.Actor);
        return (TActor)proxyInfo.ActorProxy;
    }

    /// <summary>
    /// 获取一个Actor集合，在同一分组中的服务可以调用。
    /// </summary>
    /// <typeparam name="TActor">Actor实现类或者Actor接口</typeparam>
    /// <param name="provider">Actor提供类</param>
    /// <returns></returns>
    public static async Task<List<TActor>> GetActorList<TActor>(this ActorProvider provider, IEnumerable<object> actorIds) where TActor : IActor
    {
        var list = new List<TActor>();
        if (actorIds == null || !actorIds.Any())
            return list;

        var nonCacheInfos = new List<ActorRouterInfo>();
        foreach (var actorId in actorIds)
        {
            var actorType = typeof(TActor).IsClass ? typeof(TActor) : ActorBinder.GetActorTypeByInterface(typeof(TActor));
            var interfaceType = typeof(TActor).IsClass ? ActorBinder.GetInterfaceTypeByActor(typeof(TActor)) : typeof(TActor);
            var actorIdStr = GetActorStringId(actorType, actorId);

            var typeInfo = ActorBinder.GetActorTypeInfo(actorType);
            if (typeInfo == null)
                throw new Exception($"Actor {actorType.Name} not bind.");

            IActor instanceActor = ActorBinder.GetActor(typeInfo.ServerGroup, typeInfo.ActorType, actorIdStr);
            if (instanceActor != null)
            {
                if (instanceActor.IsLocal)
                {
                    list.Add((TActor)instanceActor);
                }
                else
                {
                    list.Add((TActor)instanceActor.ProxyInfo.ActorProxy);
                }
                continue;
            }

            var routerInfo = new ActorRouterInfo
            {
                ServerGroup = typeInfo.ServerGroup,
                ActorType = typeInfo.ActorType,
                ActorId = actorIdStr,
            };
            nonCacheInfos.Add(routerInfo);
        }

        var locationContexts = await ActorProviderService.LocationClient.GetLocationContexts(nonCacheInfos);
        foreach (var locationContext in locationContexts)
        {
            if (locationContext == null)
                continue;

            var factory = new ActorProxyFactory();
            var actorType = ActorBinder.GetActorType(locationContext.ServerGroup, locationContext.ActorType);
            var interfaceType = ActorBinder.GetActorInterfaceType(locationContext.ServerGroup, locationContext.ActorType);
            var proxyInfo = factory.CreateActorProxy(actorType, interfaceType, locationContext);
            ActorBinder.AddActor(locationContext.ServerGroup, locationContext.ActorType, locationContext.ActorId, proxyInfo.Actor);

            list.Add((TActor)proxyInfo.ActorProxy);
        }
        return list;
    }

    /// <summary>
    /// 删除并取消Actor注册，在Actor被取消注册时，位置服务会告诉所有分布式服务该Actor失效。
    /// </summary>
    /// <param name="provider"></param>
    /// <param name="actorId"></param>
    public static void RemoveActor<TActor>(this ActorProvider provider, object actorId) where TActor : IActor
    {
        var actorType = typeof(TActor).IsClass ? typeof(TActor) : ActorBinder.GetActorTypeByInterface(typeof(TActor));
        var actorIdStr = GetActorStringId(actorType, actorId);

        var actor = ActorBinder.GetActor(typeof(TActor), actorIdStr);
        if (actor == null)
            return;

        ActorBinder.Remove(actor.ServerGroup, actor.ActorType, actor.ActorId);

        var routerInfo = new ActorRouterInfo
        {
            ServerGroup = actor.ServerGroup,
            ActorType = actor.ActorType,
            ActorId = actor.ActorId
        };
        RemoveActorStringId(actor.GetType(), actor.ActorId);
        ActorProviderService.LocationClient.UnRegisterActor(routerInfo);
    }

    /// <summary>
    /// 删除并取消Actor注册，在Actor被取消注册时，位置服务会告诉所有分布式服务该Actor失效。
    /// </summary>
    /// <param name="provider">Actor提供类</param>
    /// <param name="actor">Actor实例</param>
    /// <returns></returns>
    public static void RemoveActor(this ActorProvider provider, IActor actor)
    {
        if (actor == null)
            return;

        ActorBinder.Remove(actor.ServerGroup, actor.ActorType, actor.ActorId);
        var routerInfo = new ActorRouterInfo
        {
            ServerGroup = actor.ServerGroup,
            ActorType = actor.ActorType,
            ActorId = actor.ActorId
        };
        RemoveActorStringId(actor.GetType(), actor.ActorId);
        ActorProviderService.LocationClient.UnRegisterActor(routerInfo);
    }

    private static string GetActorStringId(Type actorType, object actorId)
    {
        if (actorId == null)
            return string.Empty;

        if (!actorStringIdList.TryGetValue(actorType, out ConcurrentDictionary<object, string> stringKeys))
        {
            stringKeys = new ConcurrentDictionary<object, string>();
            actorStringIdList.AddOrUpdate(actorType, stringKeys, (k, v) => stringKeys);
        }

        if (!stringKeys.TryGetValue(actorId, out string stringKey))
        {
            stringKey = actorId.ToString();
            stringKeys.AddOrUpdate(actorId, stringKey, (k, v) => stringKey);
        }

        actorIdList.TryAdd(stringKey, actorId);

        return stringKey;
    }

    internal static void RemoveActorStringId(Type actorType, string actorStringId)
    {
        if (string.IsNullOrEmpty(actorStringId))
            return;

        if (!actorIdList.TryRemove(actorStringId, out object actorId))
            return;

        if (!actorStringIdList.TryGetValue(actorType, out ConcurrentDictionary<object, string> stringKeys))
        {
            return;
        }

        stringKeys.TryRemove(actorId, out string _);
    }
}
